// 用户相关
module jinji.user.service;

import jinji.common.query;
import jinji.user.model;
import jinji.user.token;
import jinji.util.crypto;

extern (C):
/++
添加用户
Params:
	name = 用户名
	email = 邮箱
	pwd = 密码
	perm = 权限
Returns: 用户ID
+/
Id addUser(string name, string email, string pwd, Perm perm) {
	checkArgRange(perm.stringof, perm != Perm.none, "无效的权限");
	validateUser(name);
	checkUserExist(name);
	validateEmail(email);
	checkEmailExist(email);
	if (likely(has!User))
		validatePwd(pwd);
	const time = now();
	const hash = pwdHash(pwd, time);
	exec!"INSERT INTO user(name,email,pwd,perm,created_at)VALUES($1,$2,$3,$4,$5)"(
		name, email, hash[], perm, time);
	return cast(Id)db.insertId;
}

/// 验证邮箱
void validateEmail(string email) {
	import std.net.isemail;

	checkArgRange(email.stringof, email.length <= 128, "邮箱过长");
	checkArg(email.stringof, isEmail(email), text("无效的邮箱'", email, "'"));
}

/++
查找用户
Params:
	nameOrEmail = 用户名或邮箱
Throws: 用户不存在
Returns: 用户
+/
User findUser(string nameOrEmail) {
	const isEmail = nameOrEmail.canFind('@');
	(isEmail ? &validateEmail : &validateUser)(nameOrEmail);
	with (User) {
		enum sql = SB.select!(id, name, pwd, status, perm, log_times, created_at);
		auto q = query(sql.where(isEmail ? "email=$1" : "name=$1"), nameOrEmail);
		checkArg(nameOrEmail.stringof, !q.empty, "用户不存在");
		return q.get!User;
	}
}

/// 按id查找用户
alias getUser(Keys...) = getById!(User, "用户不存在", Keys);

/// 修改用户信息
@Action void setUserInfo(ref Request req, @"用户ID"long id, @"用户名" string name,
	@"邮箱" string email, @"密码" string pwd, @"权限" Perm perm) {
	import jinji.user.api;

	if (id == 0) { // 添加用户
		req.require(Perm.editUser);
		req.send(addUser(name, email, pwd, perm));
		return;
	}
	req.checkPerm(parseId(id), Perm.editUser);

	if (!name.length) { // 删除用户
		if (id) {
			req.require(Perm.editUser);
			cast()logout(id);
		} else {
			id = req.uid;
			// TODO
			//checkCaptcha(req);
			cast()logout(req.src);
		}
		req.send(deleteUser(id));
		return;
	}

	const u = getUser!"name,email,perm,created_at"(id);
	if (name != u.name)
		checkUserExist(name, id);
	if (email != u.email)
		checkEmailExist(email, id);
	// 如果修改了权限，需要超级管理员权限
	if (perm > (u.perm & perm))
		req.require(Perm.superadmin);
	if (pwd.length)
		validatePwd(pwd);
	exec!(SB.update!User.set("name=$1,email=$2,perm=$3")
			.where("id=$4"))(name, email, perm, id);
	if (pwd.length) {
		const hash = pwdHash(pwd, u.created_at);
		exec!(SB.update!User.set("pwd=$1").where("id=$2"))(hash[], id);
	}
}

/// 验证用户名
void validateUser(string name) {
	import std.ascii : isWhite;

	// TODO: 优化
	checkArgRange(name.stringof, name.length && name.length <= maxUNameLength * 3,
		"用户名为空或过长");
	checkArg(name.stringof, !isWhite(name[0]) && !isWhite(name[$ - 1]),
		"用户名首尾不能有空格");
	uint len;
	foreach (dchar c; name) {
		checkArg(name.stringof, c >= ' ' && c != '@', "用户名包含非法字符");
		len++;
	}
	checkArgRange(name.stringof, len >= 2 && len <= maxUNameLength,
		"用户名长度必须为2~18个字符");
}

/// 判断用户是否存在
void checkUserExist(string name, long uid = 0) {
	auto q = query!(SB.select!(User.id).where("name=$1"))(name);
	checkArg(name.stringof, q.empty || q.get!long == uid, "用户名已被占用，请更换其他用户名");
}

/// 判断邮箱是否存在
void checkEmailExist(string email, long uid = 0) {
	auto q = query!(SB.select!(User.id).where("email=$1"))(email);
	checkArg(email.stringof, q.empty || q.get!long == uid, "该邮箱已被占用，请更换其他邮箱");
}

/++
删除用户
Params:
	uid = 用户ID
Returns: 是否成功
+/
bool deleteUser(long uid = 0) {
	return db.delWhere!(User, "id=$1")(uid) != 0;
}

extern (D):

/// 登录
void login(ref Request req, in char[] token) {
	login(req, parseToken(token));
}

/// 登录
void login(ref Request req, in Payload token) {
	const u = getUser!"perm"(token.id);
	if (token.data > (token.data & u.perm))
		throw new Exception(_("无效的token"));

	sessions[req.src.id] = Session(token, req.src);
}

/// 验证密码
void validatePwd(in char[] pwd) {
	checkArgRange(pwd.stringof, pwd.length >= 8 && pwd.length <= 16, "密码长度必须为8~16位");
	bool[96] map;
	uint count;
	foreach (char c; pwd) {
		checkArg(pwd.stringof, c >= ' ' && c <= '\x7f', "密码包含非法字符");
		c -= ' ';
		if (!map[c]) {
			map[c] = true;
			count++;
		}
	}
	checkArg(pwd.stringof, count >= 3, "密码过于简单");
}

/// 验证密码
void checkPwd(const User u, in char[] pwd) {
	if (pwdHash(pwd, u.created_at) != u.pwd)
		throw new Exception(_("用户名或密码错误"));
}

/++
登出
Returns: 是否成功
+/
bool logout(WSClient client, string msg = "") {
	if (msg != "")
		client.send(msg);
	scope (exit) {
		sessions.remove(client.id);
	}
	return true;
}

/// ditto
bool logout(long uid, string msg = "") {
	foreach (sess; sessions)
		if (sess.uid == uid)
			return logout(sess.client, msg);
	return false;
}
